package com.biscuit.page.basicservice.conf.aop;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.biscuit.common.base.BaseResponse;
import com.biscuit.page.basicservice.api.model.request.authorization.AuthorizationAReq;
import com.biscuit.page.basicservice.api.model.response.authorization.AuthorizationAResp;
import com.biscuit.page.basicservice.conf.handler.RequestContextHandler;
import com.biscuit.page.basicservice.model.entities.mysql.SysUserLogin;
import com.biscuit.page.basicservice.model.enums.LoginTypeEnum;
import com.biscuit.page.basicservice.persistence.dao.UserLoginDao;
import com.biscuit.page.basicservice.utils.BaseThreadUtil;
import com.biscuit.page.basicservice.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author mazihao
 * @createTime 2024/3/30 9:52
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    @Resource
    private UserLoginDao userLoginDao;

    /**
     * 拦截 service 包下所有类中的所有公共方法
     */
    @Pointcut("execution(public * com.biscuit.page.basicservice.service.*.*(..))")
    public void allServiceMethod() {
    }

    /**
     * 拦截登录方法
     */
    @Pointcut("@annotation(com.biscuit.page.basicservice.anno.LoginLog)")
    public void loginMethod() {
    }

    /**
     * 前置拦截所有 service 方法，统一打印日志
     */
    @Before("allServiceMethod()")
    public void printLogBefore(JoinPoint point) {
        // 获取目标方法所在类的类名
        String className = point.getSignature().getDeclaringTypeName();
        // 获取目标方法的方法名
        String methodName = point.getSignature().getName();
        // 获取目标方法参数列表，使用 IntStream.range 生成一个与数组长度相同的流
        Map<String, Object> paramMap;
        if (CollUtil.isEmpty(Arrays.asList(point.getArgs())) || Objects.isNull(point.getArgs()[0])) {
            paramMap = MapUtil.empty();
        } else {
            paramMap = IntStream.range(0, point.getArgs().length)
                    .boxed()
                    .collect(Collectors.toMap(Object::toString, v -> Objects.nonNull(point.getArgs()[v]) ? point.getArgs()[v] : new Object()));
        }
        // 打印日志
        if (paramMap.isEmpty()) {
            log.info("{} {} - {} start，not params", className, methodName, DateUtil.getCurrentDateTime());
        } else {
            log.info("{} {} - {} start，params -> {}", className, methodName, DateUtil.getCurrentDateTime(), JSON.toJSONString(paramMap.get(0)));
        }
    }

    /**
     * 返回拦截所有 service 方法，当 service 正常返回时统一打印日志
     */
    @AfterReturning(value = "allServiceMethod()", returning = "resultObj")
    public void printLogAfter(JoinPoint point, Object resultObj) {
        // 获取目标方法所在类的类名
        String className = this.getClassByPoint(point);
        // 获取目标方法的方法名
        String methodName = this.getMethodByPoint(point);
        // 打印日志
        if (Objects.isNull(resultObj)) {
            log.info("{} {} - {} end，not response", className, methodName, DateUtil.getCurrentDateTime());
        } else {
            log.info("{} {} - {} end，response -> {}", className, methodName, DateUtil.getCurrentDateTime(), JSON.toJSONString(resultObj));
        }
    }

    /**
     * 返回拦截所有 service 方法，当 service 方法抛出异常时统一打印日志
     */
    @AfterThrowing(value = "allServiceMethod()", throwing = "ex")
    public void printLogAfter(JoinPoint point, Exception ex) throws Exception {
        // 获取目标方法所在类的类名
        String className = this.getClassByPoint(point);
        // 获取目标方法的方法名
        String methodName = this.getMethodByPoint(point);
        // 打印日志
        log.error("{} {} - {} error，message -> {}，class -> {}", className, methodName, DateUtil.getCurrentDateTime(), ex.getMessage(), JSON.toJSONString(ex));
        // 继续抛出异常，通过全局异常处理器返回正确的数据格式
        throw ex;
    }

    @Around("loginMethod()")
    public Object accountLoginLog(ProceedingJoinPoint point) throws Throwable {
        // 获取方法参数
        AuthorizationAReq req = (AuthorizationAReq) point.getArgs()[0];

        // 获取登录方式
        LoginTypeEnum loginTypeEnum = LoginTypeEnum.getTypeEnumByType(req.getLoginType());

        // 创建日志对象
        SysUserLogin sysUserLogin = new SysUserLogin();
        sysUserLogin.setAccount(req.getAccount());
        sysUserLogin.setParams(JSON.toJSONString(req));
        sysUserLogin.setIp(RequestContextHandler.getContext().getIp());
        sysUserLogin.setType(loginTypeEnum.getType());

        StringBuilder sb = new StringBuilder(loginTypeEnum.getDesc());

        try {
            // 执行业务逻辑
            Object resultObj = point.proceed();

            // 类型转换并取值
            BaseResponse<AuthorizationAResp> result = (BaseResponse<AuthorizationAResp>) resultObj;

            // 登录成功
            sb.append("-成功");
            sysUserLogin.setUserId(result.getData().getInfo().getId());
            sysUserLogin.setResult(JSON.toJSONString(result));

            // 返回响应内容
            return resultObj;
        } catch (Throwable ex) {
            // 登录失败
            sb.append("-成功");
            sysUserLogin.setErrorMessage(ex.getMessage());
            throw ex;
        } finally {
            // 异步记录登录日志
            sysUserLogin.setDescription(sb.toString());
            BaseThreadUtil.async(() -> this.userLoginDao.save(sysUserLogin));
        }
    }

    private String getClassByPoint(JoinPoint point) {
        return point.getSignature().getDeclaringTypeName();
    }

    private String getMethodByPoint(JoinPoint point) {
        return point.getSignature().getName();
    }


}
